package com.gdzn.netty.server;

import com.gdzn.netty.api.config.NettyProperties;
import com.gdzn.netty.handler.TcpHeartBeatHandler;
import com.gdzn.netty.server.handler.JsonDecoder;
import com.gdzn.netty.server.handler.JsonEncoder;
import com.gdzn.netty.server.handler.TcpDevicelHandler;
import com.gdzn.netty.service.IDeviceService;
import io.netty.bootstrap.ServerBootstrap;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.ChannelOption;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.nio.NioServerSocketChannel;
import io.netty.channel.socket.nio.NioSocketChannel;
import io.netty.handler.timeout.IdleStateHandler;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

import javax.annotation.PostConstruct;
import java.util.Date;

/**
 * @author: zdj
 * @created: 2024-04-15
 * @version: V1.0
 */
@Slf4j
@Component
public class NettyTcpServer {

    private final NettyProperties nettyProperties;

    @Autowired
    private IDeviceService deviceService;

    @Autowired
    public NettyTcpServer(NettyProperties nettyProperties,IDeviceService deviceService) {
        this.nettyProperties = nettyProperties;
        this.deviceService = deviceService;
    }

    @PostConstruct
    public void start() {
        try {
            //bossGroup表示监听端口，accept 新连接的线程组
            NioEventLoopGroup boosGroup = new NioEventLoopGroup(nettyProperties.getBoss_group());
            //workerGroup表示处理每一条连接的数据读写的线程组
            NioEventLoopGroup workerGroup = new NioEventLoopGroup(nettyProperties.getWorker_count());
            log.info("host:【{}】,监听端口:{},boss线程组:{},work工作线程组:{}", nettyProperties.getHost(), nettyProperties.getPort(), nettyProperties.getBoss_group(), nettyProperties.getWorker_count());
            ServerBootstrap serverBootstrap = new ServerBootstrap();
            serverBootstrap
                    //.group(bossGroup, workerGroup)给引导类配置两大线程组，这个引导类的线程模型也就定型了
                    .group(boosGroup, workerGroup)
                    //来指定 NIO 模型，当
                    .channel(NioServerSocketChannel.class)
                    //backlog 用于构造服务端套接字ServerSocket对象，标识当服务器请求处理线程全满时，用于临时存放已完成三次握手的请求的队列的最大长度
                    //如果未设置或所设置的值小于1，Java将使用默认值50
                    //如果队列(A+B)的长度大于Backlog时，新的连接就会被TCP内核拒绝掉。
                    // 服务器TCP内核 内维护了两个队列，称为A(未连接队列)和B(已连接队列)
                    .option(ChannelOption.SO_BACKLOG, nettyProperties.getBack_log())
                    //当设置保持心跳该选项以后，如果在两小时内没有数据的通信时，TCP会自动发送一个活动探测数据报文。
                    .childOption(ChannelOption.SO_KEEPALIVE, nettyProperties.getSo_keepalive())
                    //该参数=true的作用就是禁止使用Nagle算法，使用于小数据即时传输,
                    // 而false时Nagle算法是将小的数据包组装为更大的帧然后进行发送，而不是输入一次发送一次，因此在数据包不足的时候会等待其他数据的到来，组装成大的数据包进行发送，虽然该算法有效提高了网络的有效负载，但是却造成了延时
                    .childOption(ChannelOption.TCP_NODELAY, nettyProperties.getTcp_nodelay())
                    .childHandler(new ChannelInitializer<NioSocketChannel>() {
                        //childHandler()方法，给这个引导类创建一个ChannelInitializer，这里主要就是定义后续每条连接的数据读写，业务处理逻辑
                        @Override
                        protected void initChannel(NioSocketChannel ch) {
//                            ch.pipeline().addLast(new DelimiterBasedFrameDecoder(1024, Delimiters.lineDelimiter()));
                            //ch.pipeline().addLast(new StringDecoder(Charset.forName("UTF-8")));
                            ch.pipeline().addLast(new JsonDecoder());
                            //ch.pipeline().addLast(new Gps16Decoder());
                           // ch.pipeline().addLast(new StringEncoder());
                            ch.pipeline().addLast(new JsonEncoder());
                            //ch.pipeline().addLast(new Gps16Encoder());
                            // 空闲检测
                            ch.pipeline().addLast(new IdleStateHandler(nettyProperties.getHeart_read_time(), 0,  0));
                            ch.pipeline().addLast(new TcpHeartBeatHandler(deviceService));
                            ch.pipeline().addLast(new TcpDevicelHandler());
//                            ch.pipeline().addLast(GpsTestLoginRequestHandler.INSTANCE);
                        }
                    });

            bind(serverBootstrap, nettyProperties.getHost(), nettyProperties.getPort());
        }catch (Exception e){
            e.printStackTrace();
        }
    }

    private void bind(final ServerBootstrap serverBootstrap, final String host, final int port) {
        try {
            serverBootstrap.bind(host, port).addListener(future -> {
                if (future.isSuccess()) {
                    log.info("netty服务端,时间:{},host:{},端口:{},绑定成功!", new Date(), host, port);
                } else {
                    log.info("netty服务端,时间:{},host:{},端口：{},绑定失败!!", new Date(), host, port);
                    //逻辑里面重新绑定一个递增的端口号，接下来，我们把这段绑定逻辑抽取出一个bind方法
                    //bind(serverBootstrap, host, port + 1);
                }
            });
        }catch (Exception e){
            e.printStackTrace();
        }
    }

}
